# Copyright (c) 2010, 2024, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation. The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

cmake_minimum_required(VERSION 3.0)

#
# Build MSI package from files installed in CMAKE_INSTALL_PREFIX location.
#
# Usage:
#  cmake -D CMAKE_INSTALL_PREFIX=<install loc>
#        -D INSTALL_MANIFEST=<manifest file> ... <src>/packaging/WiX
#  cmake --build . --target MSI
#  cmake --build . --target ZIP
#
# This should be invoked (in a dedicated location) after building and
# installing main connector project. CMAKE_INSTALL_PREFIX should point
# at install location of the main project. INSTALL_MANIFEST should point
# at install manifest file generated by the builds (this happens if the
# same INSTALL_MANIFEST option is given together with WITH_PACKAGES during
# the builds). The manifest file describes install components defined in
# the project.
#
# Generating MSI package
# ----------------------
# During configuration time, based on the available information, this
# project generates connector-cpp.wxs file from the .wxs.in template.
# This file is then used by WiX tools invoked from the MSI target to
# build the pacakge.
#
# Some other options might need to be specified to correctly generate
# the .wxs definition. For example STATIC_MSVCRT or
# BUNDLE_RUNTIME_LIBRARIES. Otherwise most of the information is taken
# from the install manifest and version.cmake, packaging/PackageSpecs.cmake
# files in the main project sources.
#
# Generating ZIP package
# ----------------------
# ZIP package simply includes all the files from the given install
# location.
#

IF(NOT WIN32)
  message(FATAL_ERROR "This project is only for Windows platform")
ENDIF()

#
# Check that we are invoked from the main source tree
#

get_filename_component(BASE_DIR "../.." REALPATH)
message("BASE_DIR: ${BASE_DIR}")

if(NOT EXISTS "${BASE_DIR}/packaging/WiX")
  message(FATAL_ERROR
    "This CMakeList.txt should be used from within packagin/WiX foler"
    " within Connector/C++ source tree. Looks that this is not the case."
  )
endif()

#
# Check the install location and the manifest file
#

file(TO_CMAKE_PATH ${CMAKE_INSTALL_PREFIX} INSTALL_DIR)

if(NOT EXISTS ${INSTALL_MANIFEST})
  message(FATAL_ERROR
    "Could not find specified install manifest file:"
    " ${INSTALL_MANIFEST}"
  )
endif()


# Project definition
##########################################################################

project(Build_packages NONE)


macro(main)

# Determine 64 vs. 33 bit by looking at library install location (lib/ vs. lib64/)
# Note: should be done before including PackageSpecs.cmake

file(GLOB IS64BIT "${INSTALL_DIR}/lib*")

if(NOT IS64BIT)
  message(FATAL_ERROR "Could not determine if build is 64 bit (no lib*/ folder inside ZIP?)")
endif()

get_filename_component(IS64BIT ${IS64BIT} NAME)

if(IS64BIT MATCHES "lib64")
  set(IS64BIT true)
  set(BITNESS "always64")
else()
  set(IS64BIT false)
  set(BITNESS "always32")
endif()

#
# Get version and package info from the main project.
#

include(${BASE_DIR}/version.cmake)
include(${BASE_DIR}/packaging/PackageSpecs.cmake)


###################################
# MSI package
###################################

#
# Set variables used in .wxs.in template.
#

SET(MANUFACTURER "Oracle Corporation")
SET(PRODUCT_NAME "MySQL Connector/C++")
SET(PRODUCT_DESCRIPTION "MySQL Connector/C++")

set(MAJOR_VERSION ${CONCPP_VERSION_MAJOR})
set(MINOR_VERSION ${CONCPP_VERSION_MINOR})
set(PATCH_VERSION ${CONCPP_VERSION_MICRO})

set(WIX_INSTALL_BASE "MySQL Connector C++")
set(WIX_INSTALL_DIR "${WIX_INSTALL_BASE} ${MAJOR_VERSION}.${MINOR_VERSION}")


# **** IMPORTANT ****
#
# The code below needs to be replaced when moving from one version
# series to another. I.e. when moving from 4.0 to 4.1, from 4.13 to
# 5.0 and so on.
#
# You DON'T change this code for patchlevel version changes, i.e.
# when only the third part of the version is changed.
#
# You can use any GUID generator that produces random GUID codes. You
# can also or invent a code of your own if you follow the syntax rules.

#set(CONCPP_MINORMAJOR_UPGRADE_CODE "a1195164-bc2d-45fb-a5e5-1ba834771ce8")

SET(UPGRADE_CODE_32_BIT "C5071C05-F2D8-4A7D-A089-F3347B2C720F")
SET(UPGRADE_CODE_64_BIT "9CE29674-49E6-4D9D-B424-E851CC983906")
SET(UPGRADE_CODE_OLD "FE990D78-8BB1-4880-930A-0430E707F3CA")

set(LICENSE_RTF "${INSTALL_DIR}/LICENSE.rtf")
# Generate .rtf from .txt, if not already present
generate_rtf(${LICENSE_RTF})


if(STATIC_MSVCRT)
  set(STATIC_MSVCRT "ON")
else()
  set(STATIC_MSVCRT "OFF")
endif()
show(STATIC_MSVCRT)

if(BUNDLE_RUNTIME_LIBRARIES)
  set(BUNDLE_RUNTIME_LIBRARIES "ON")
else()
  set(BUNDLE_RUNTIME_LIBRARIES "OFF")
endif()
show(BUNDLE_RUNTIME_LIBRARIES)


if(IS64BIT)
  set(IS64BIT "yes")
  set(PLATFORM x64)
  set(UPGRADE_CODE "${UPGRADE_CODE_64_BIT}")
  set(PROGRAM_FILES_FOLDER ProgramFiles64Folder)
else()
  set(IS64BIT "no")
  set(PLATFORM x86)
  set(UPGRADE_CODE "${UPGRADE_CODE_32_BIT}")
  set(PROGRAM_FILES_FOLDER ProgramFilesFolder)
endif()
show(IS64BIT)


if(NOT WIX_UI)
  set(WIX_UI "WixUI_Mondo_Custom")
endif()

list(APPEND WIX_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/custom_ui.wxs")

# Location of other files used to build MSI such as icons etc.

set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})

#
# Generate WIX_DIRECTORIES, WIX_COMPONENTS and WIX_COMPONENT_GROUPS
# fragments used in connector-cpp.wxs.in based on information from
# install manifest.
#

include(${INSTALL_MANIFEST})
set_wix_components()

#
# Generate .wxs definition from the template
#

configure_file(connector-cpp.wxs.in  ${CMAKE_CURRENT_BINARY_DIR}/connector-cpp.wxs)

# Clean up cache

set(WIX_DIRECTORIES "" CACHE STRING "output var" FORCE)
set(WIX_COMPONENTS "" CACHE STRING "output var" FORCE)
set(WIX_COMPONENT_GROUPS "" CACHE STRING "output var" FORCE)
set(WIX_INCLUDES "" CACHE STRING "output var" FORCE)

#
# Target for building MSI package
#

include(wix_setup.cmake)

set(EXTRA_WIX_ARGS $ENV{EXTRA_WIX_ARGS})

add_custom_target(MSI
  COMMAND ${WIX_EXECUTABLE} build -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext -arch ${PLATFORM} connector-cpp.wxs
    -out  ${CMAKE_BINARY_DIR}/${CPACK_PACKAGE_FILE_NAME}.msi
    ${EXTRA_WIX_ARGS}
)


###################################
# ZIP package
###################################

#
#  We use cpack to generate ZIP package. All the files from INSTALL_DIR
#  are "installed" again, and this tells cpack to include them in
#  the generated ZIP package.
#
#  Note: All required cpack settings are defined in PackageSpecs.cmake
#  included above.
#

set(CPACK_GENERATOR ZIP)

install(DIRECTORY ${INSTALL_DIR}/
  DESTINATION .
  PATTERN "install_manifest.cmake" EXCLUDE
  PATTERN "LICENSE.rtf" EXCLUDE
)

include(CPack)

#
# Target for building ZIP package
#
# We simply build the 'package' target that is defined by CPack
#

add_custom_target(ZIP
  COMMAND ${CMAKE_COMMAND} --build . --target package
  WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
)


endmacro(main)

########################################################################


#
# Set WIX_DIRECTORIES, WIX_COMPONENTS and WIX_COMPONENT_GROUPS
# variables to .wxs fragments that define WiX components and files
# they contain.
#

function(set_wix_components)

  # Parse information from install manifest, setting the following
  # variables:
  #
  # - DIR_LIST  -- list of install directory suffixes
  # - DIRS${D}  -- list of sub-directories in a directory with suffix ${D}
  # - FILES${C}${D} -- list of files from install component ${C} inside
  #                    directory with suffix ${D}
  # - GROUP${C} -- list of suffixes of directories which have files from
  #                comonent ${C}
  #
  # - PATH${D}_${F} -- path to file F in directory with suffix ${D}
  #

  parse_install_info()

  # Define direcrtories

  set(OUT_VAR WIX_DIRECTORIES)

  out("<DirectoryRef Id='INSTALLDIR'>")
  define_dirs("  ")
  out("</DirectoryRef>")

  # Define components
  # Note: Currently we do not put Debuginfo component into MSI, only ZIP

  set(COMPONENTS ${COMPONENTS})
  list(REMOVE_ITEM COMPONENTS "Debuginfo")

  set(OUT_VAR WIX_COMPONENTS)

  foreach(d_suffix ${DIR_LIST})

    get_dir_id(${d_suffix} d_id)

    foreach(C ${COMPONENTS})

      if(NOT FILES${C}${d_suffix})
        continue()
      endif()

      get_comp_id(${C}${d_suffix} c_id)
      generate_guid(GUID)

      out("<DirectoryRef Id='${d_id}'>")
      out("  <Component Id='${c_id}' Guid='${GUID}' Bitness='${BITNESS}'>")

      foreach(F ${FILES${C}${d_suffix}})
        get_file_id(${d_suffix}_${F} id path)
        out("    <File Id='${id}' Source='${INSTALL_DIR}/${path}' />")
      endforeach()

      out("  </Component>")
      out("</DirectoryRef>")

    endforeach()

  endforeach()

  # Define component groups

  set(OUT_VAR WIX_COMPONENT_GROUPS)

  foreach(C ${COMPONENTS})
    if(NOT GROUP${C})
      continue()
    endif()
    out("<ComponentGroup Id='componentgroup.${C}'>")
    foreach(d_suffix ${GROUP${C}})
      get_comp_id(${C}${d_suffix} c_id)
      out("  <ComponentRef Id='${c_id}' />")
    endforeach()
    out("</ComponentGroup>")
  endforeach()

  # Includes

  set(OUT_VAR WIX_INCLUDES)

  foreach(I ${WIX_INCLUDE})
    out("<?include ${I} ?>")
  endforeach()

endfunction(set_wix_components)


#
# Parse information from install manifest (COMPONENTS, FILES_${C}) and
# extract it into variables expected by set_wix_components()
#

macro(parse_install_info)

  set(DIRS)

  foreach(C ${COMPONENTS})

    if(C STREQUAL "Auxiliary" OR NOT FILES_${C})
      continue()
    endif()

    #message("- component: ${C}")

    foreach(F ${FILES_${C}})

      #message("-- file: ${F}")
      get_filename_component(F_name ${F} NAME)
      get_filename_component(F_path ${F} PATH)

      # transform path to a list of folder names, for example
      # if F_path is /foo/bar/baz then F_list will be [foo,bar,baz]

      set(F_list)
      get_path(F_list ${F_path})
      #message("-- path: ${F_list} > ${F_name}")

      # Add all components of the path to the DIRS${suffix}
      # lists which list subdirectories of each directory.
      # In the example above, with F_list equal [foo,bar,baz]
      # this will happen:
      #
      # - "foo" is added to DIRS list,
      # - "bar" is added to DIRS_foo list,
      # - "baz" is added to DIRS_foo_bar list
      #
      # After this ${suffix} is _foo_bar_baz and corresponds
      # to the full path of the file.

      set(suffix "")
      foreach(D ${F_list})
        list(APPEND DIRS${suffix} ${D})
        list(REMOVE_DUPLICATES DIRS${suffix})
        set(suffix "${suffix}_${D}")
      endforeach()

      # Add file name to the FILES${suffix} list of files
      # at path given by ${suffix}. Also store the actual path
      # to the file in PATH...

      list(APPEND FILES${C}${suffix} ${F_name})
      set(PATH${suffix}_${F_name} ${F})

      # Add file path suffix to the list of all directories
      # and to the list of directories for the component ${C}

      list(APPEND DIR_LIST ${suffix})
      list(REMOVE_DUPLICATES DIR_LIST)

      list(APPEND GROUP${C} ${suffix})
      list(REMOVE_DUPLICATES GROUP${C})

    endforeach()

  endforeach()

endmacro(parse_install_info)


#
# Output directory definitions (recursive)
#

function(define_dirs)

  set(indent ${ARGV0})
  set(suffix ${ARGV1})

  foreach(D ${DIRS${suffix}})

    set(d_suffix "${suffix}_${D}")

    if(d_suffix STREQUAL "_.")
      continue()
    endif()

    get_dir_id(${d_suffix} id)
    out("${indent}<Directory Id='${id}' Name='${D}'>")
    define_dirs("${indent}  " ${d_suffix})
    out("${indent}</Directory>")
  endforeach()

endfunction(define_dirs)


#
# Generate WiX identifiers for different objects
#

function(get_comp_id SUFFIX ID)
  make_wix_identifier("${SUFFIX}" id)
  set(${ID} "C.${id}" PARENT_SCOPE)
endfunction()

function(get_dir_id SUFFIX ID)
  if(SUFFIX STREQUAL "_.")
    set(${ID} "INSTALLDIR" PARENT_SCOPE)
  else()
    make_wix_identifier("${SUFFIX}" id)
    set(${ID} "D.${id}" PARENT_SCOPE)
  endif()
endfunction()


function(get_file_id SUFFIX ID PATH)
  make_wix_identifier("${SUFFIX}" id)
  set(${ID} "F.${id}" PARENT_SCOPE)
  set(${PATH} ${PATH${SUFFIX}} PARENT_SCOPE)
endfunction()


function(make_wix_identifier STR VAR)
  string(MD5 out ${STR})
  string(MAKE_C_IDENTIFIER ${STR} STR)
  string(LENGTH ${STR} len)
  if(len GREATER 30)
    math(EXPR len ${len}-30)
    string(SUBSTRING ${STR} ${len} -1 STR)
  endif()
  set(${VAR} "${STR}.${out}" PARENT_SCOPE)
endfunction()


#
#  Generate UUIDs
#

function(generate_guid VAR)

  string(RANDOM x)
  string(UUID guid NAMESPACE "96122528-4F58-40F8-AB22-96F9853460F8" NAME "${x}" TYPE MD5 UPPER)
  # message(STATUS "generated guid: ${guid}")
  set(${VAR} ${guid} PARENT_SCOPE)

endfunction()


#
# Convert path into a list
#

function(get_path OUT PATH)

  get_filename_component(top ${PATH} NAME)
  get_filename_component(leading ${PATH} PATH)
  if(leading AND NOT (leading STREQUAL "."))
    get_path(${OUT} ${leading})
  endif()
  list(APPEND ${OUT} ${top})
  set(${OUT} ${${OUT}} PARENT_SCOPE)

endfunction()


#
# If the given .rtf file does not exist, try to generate it from
# a corresponding .txt file.
#

function(generate_rtf FILE)

  if(EXISTS ${FILE})
    return()
  endif()

  get_filename_component(f_name ${FILE} NAME_WE)
  get_filename_component(f_path ${FILE} PATH)

  find_file(${f_name}_TXT "${f_name}.txt"
    PATHS ${f_path}
    PATH_SUFFIXES "." "../.."
    NO_DEFAULT_PATH
  )

  if(NOT EXISTS ${${f_name}_TXT})
    return()
  endif()

  FILE(READ ${${f_name}_TXT} CONTENTS)
  STRING(REGEX REPLACE "\n" "\\\\par\n" CONTENTS "${CONTENTS}")
  STRING(REGEX REPLACE "\t" "\\\\tab" CONTENTS "${CONTENTS}")
  FILE(WRITE ${FILE} "{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0\\fnil\\fcharset0 Courier New;}}\\viewkind4\\uc1\\pard\\lang1031\\f0\\fs15")
  FILE(APPEND ${FILE} "${CONTENTS}")
  FILE(APPEND ${FILE} "\n}\n")

endfunction()


#
# Append string to OUT_VAR
#

macro(out)
  if(NOT OUT_VAR)
    message(FATAL_ERROR "Output variable not defined")
  endif()
  #message(STATUS "${ARGV}")
  set(${OUT_VAR} "${${OUT_VAR}}${ARGV}\n" CACHE STRING "output variable" FORCE)
endmacro()


function(show VAR)
  message("- ${VAR}: ${${VAR}}")
endfunction()


main()
return()
